Spring中的事务管理
数据库事务
数据库事务(Transaction,简写为TX)是数据库管理系统执行过程中的一个逻辑单位,是可以提交或回滚的工作的原子单元。当事务对数据库进行多次更改时,要么在提交事务时所有更改都成功,要么在回滚事务时所有更改都被撤消。
数据库(包括但不限于关系型)事务一般拥有以下4个特性,称之为ACID特性
ACID
- 原子性(Atomicity):事务作为一个整体被执行,包含在其中的对数据库的操作要么全部被执行,要么都不执行。
- 一致性(Consistency):事务应确保数据库的状态从一个一致状态转变为另一个一致状态。_一致状态_的含义是数据库中的数据应满足完整性约束。
- 隔离性(Isolation):多个事务并发执行时,一个事务的执行不应影响其他事务的执行。
- 持久性(Durability):已被提交的事务对数据库的修改应该永久保存在数据库中。
Mysql中的事务
START TRANSACTION
[transaction_characteristic [, transaction_characteristic] ...]
transaction_characteristic: {
WITH CONSISTENT SNAPSHOT
| READ WRITE
| READ ONLY
}
BEGIN [WORK]
COMMIT [WORK] [AND [NO] CHAIN] [[NO] RELEASE]
ROLLBACK [WORK] [AND [NO] CHAIN] [[NO] RELEASE]
SET autocommit = {0 | 1}
START TRANSACTION
或BEGIN
开始新事务。COMMIT
提交当前事务。ROLLBACK
回滚当前事务。SET autocommit
禁用或启用当前会话的默认自动提交模式。
默认情况下,Mysql是自动提交的模式,所有语句会立即提交
**
JDBC 中的事务
JDBC是Java语言中用来规范客户端程序如何来访问数据库的应用程序接口,提供了查询和更新数据库中数据的方法。JDBC也是Sun Microsystems的商标(现在属于Oracle),是面向关系型数据库的。
上面说到,Mysql是默认自动提交的,所以JDBC中事务事务的第一步,需要禁用自动提交:
con.setAutoCommit(false);
提交事务:
con.commit();
回滚事务:
con.rollback();
一个完整流程的例子(摘自Oracle JDBC文档):
public void updateCoffeeSales(HashMap<String, Integer> salesForWeek)
throws SQLException {
PreparedStatement updateSales = null;
PreparedStatement updateTotal = null;
String updateString =
"update " + dbName + ".COFFEES " +
"set SALES = ? where COF_NAME = ?";
String updateStatement =
"update " + dbName + ".COFFEES " +
"set TOTAL = TOTAL + ? " +
"where COF_NAME = ?";
try {
con.setAutoCommit(false);
updateSales = con.prepareStatement(updateString);
updateTotal = con.prepareStatement(updateStatement);
for (Map.Entry<String, Integer> e : salesForWeek.entrySet()) {
updateSales.setInt(1, e.getValue().intValue());
updateSales.setString(2, e.getKey());
updateSales.executeUpdate();
updateTotal.setInt(1, e.getValue().intValue());
updateTotal.setString(2, e.getKey());
updateTotal.executeUpdate();
con.commit();
}
} catch (SQLException e ) {
JDBCTutorialUtilities.printSQLException(e);
if (con != null) {
try {
System.err.print("Transaction is being rolled back");
con.rollback();
} catch(SQLException excep) {
JDBCTutorialUtilities.printSQLException(excep);
}
}
} finally {
if (updateSales != null) {
updateSales.close();
}
if (updateTotal != null) {
updateTotal.close();
}
con.setAutoCommit(true);
}
}
为什么需要事务管理器
如果没有事务管理器的话,我们的程序可能是这样:
Connection connection = acquireConnection();
try{
int updated = connection.prepareStatement().executeUpdate();
connection.commit();
}catch (Exception e){
rollback(connection);
}finally {
releaseConnection(connection);
}
也有可能是这样"优雅的事务":
execute(new TxCallback() {
@Override
public Object doInTx(Connection var1) {
//do something...
return null;
}
});
public void execute(TxCallback txCallback){
Connection connection = acquireConnection();
try{
txCallback.doInTx(connection);
connection.commit();
}catch (Exception e){
rollback(connection);
}finally {
releaseConnection(connection);
}
}
# lambda版
execute(connection -> {
//do something...
return null;
});
但是以上两种方式,针对一些复杂的场景是很不方便的。在实际的业务场景中,往往有比较复杂的业务逻辑,代码冗长,逻辑关联复杂,如果一个大操作中有全是这种代码的话我想开发人员可能会疯把。更不用提定制化的隔离级别,以及嵌套/独立事务的处理了。
Spring 事务管理器(Transaction Manager)简介
Spring作为Java最强框架,事务管理也是其核心功能之一。Spring为事务管理提供了统一的抽象,有以下优点:
- 跨不同事务API(例如Java事务API(JTA),JDBC,Hibernate,Java持久性API(JPA)和Java数据对象(JDO))的一致编程模型。
- 支持声明式事务管理(注解形式)
- 与JTA之类的复杂事务API相比, 用于程序化事务管理的API更简单
- 和Spring的Data层抽象集成方便(比如Spring - Hibernate/Jdbc/Mybatis/Jpa...)
Spring的事务管理器只是一个接口/抽象,不同的DB层框架(其实不光是DB类框架,支持事务模型的理论上都可以使用这套抽象) 可能都需要实现此标准才可以更好的工作,核心接口是org.springframework.transaction.support.AbstractPlatformTransactionManager
,其代码位于spring-tx
模块中,比如Hibernate中的实现为:org.springframework.orm.hibernate4.HibernateTransactionManager
使用方式
事务,自然是控制业务的,在一个业务流程内,往往希望保证原子性,要么全成功要么全失败。
所以事务一般是加载@Service
层,一个Service内调用了多个操作数据库的操作(比如Dao),在Service结束后事务自动提交,如有异常抛出则事务回滚。
注解
在被Spring管理的类头上增加@Transactional
注解,即可对该类下的所有方法开启事务管理。事务开启后,方法内的操作无需手动开启/提交/回滚事务,一切交给Spring管理即可。
@Service
@Transactional
public class TxTestService{
@Autowired
private OrderRepo orderRepo;
public void submit(Order order){
orderRepo.save(order);
}
}
也可以只在方法上配置,方法配置的优先级是大于类的
@Service
public class TxTestService{
@Autowired
private OrderRepo orderRepo;
@Transactional
public void submit(Order order){
orderRepo.save(order);
}
}
TransactionTemplate
TransactionTemplate这中方式,其实和使用注解形式的区别不大,其核心功能也是由TransactionManager实现的,这里只是换了个入口
public <T> T execute(TransactionCallback<T> action) throws TransactionException {
if (this.transactionManager instanceof CallbackPreferringPlatformTransactionManager) {
return ((CallbackPreferringPlatformTransactionManager) this.transactionManager).execute(this, action);
}
else {
//获取事务信息
TransactionStatus status = this.transactionManager.getTransaction(this);
T result;
try {
//执行业务代码
result = action.doInTransaction(status);
}
//处理异常回滚
catch (RuntimeException ex) {
// Transactional code threw application exception -> rollback
rollbackOnException(status, ex);
throw ex;
}
catch (Error err) {
// Transactional code threw error -> rollback
rollbackOnException(status, err);
throw err;
}
catch (Exception ex) {
// Transactional code threw unexpected exception -> rollback
rollbackOnException(status, ex);
throw new UndeclaredThrowableException(ex, "TransactionCallback threw undeclared checked exception");
}
//提交事务
this.transactionManager.commit(status);
return result;
}
}
XML配置<tx:advice>
隔离级别(Isolation Level)
事务隔离级别是数据库最重要的特性之一,他保证了脏读/幻读等问题不会发生。作为一个事务管理框架自然也是支持此配置的,在@Transactional注解中有一个isolation配置,可以很方便的配置各个事务的隔离级别,等同于connection.setTransactionIsolation()
Isolation {
DEFAULT(-1),
READ_UNCOMMITTED(1),
READ_COMMITTED(2),
REPEATABLE_READ(4),
SERIALIZABLE(8);
}
传播行为(Propagation behavior)
可能没有接触过Spring的人听到传播行为会奇怪,这是个什么东西。
其实这个传播行为和数据库功能无关,只是事务管理器为了处理复杂业务而设计的一个机制。
比如现在有这样一个调用场景,A Service -> B Service -> C Service
,但是希望A/B在一个事务内,C是一个独立的事务,同时C如果出错,不影响AB所在的事务。
此时,就可以通过传播行为来处理;将C Service的事务配置为@Transactional(propagation = Propagation.REQUIRES_NEW)
即可
REQUIRED
默认策略,优先使用当前事务(及当前线程绑定的事务资源),如果不存在事务,则开启新事务
SUPPORTS
优先使用当前的事务(及当前线程绑定的事务资源),如果不存在事务,则以无事务方式运行
MANDATORY
优先使用当前的事务,如果不存在事务,则抛出异常
REQUIRES_NEW
创建一个新事务,如果存在当前事务,则挂起(Suspend)
NOT_SUPPORTED
以非事务方式执行,如果当前事务存在,则挂起当前事务。
NEVER
以非事务方式执行,如果当前事务存在,则抛出异常
回滚策略
@Transactional中有4个配置回滚策略的属性,分为Rollback策略,和NoRollback策略
默认情况下,RuntimeException和Error这两种异常会导致事务回滚,普通的Exception(需要Catch的)异常不会回滚。
Rollback
配置需要回滚的异常类
# 异常类Class
Class<? extends Throwable>[] rollbackFor() default {};
# 异常类ClassName,可以是FullName/SimpleName
String[] rollbackForClassName() default {};
NoRollback
针对一些要特殊处理的业务逻辑,比如插一些日志表,或者不重要的业务流程,希望就算出错也不影响事务的提交。
可以通过配置NoRollbackFor来实现,让某些异常不影响事务的状态。
# 异常类Class
Class<? extends Throwable>[] noRollbackFor() default {};
# 异常类ClassName,可以是FullName/SimpleName
String[] noRollbackForClassName() default {};
只读控制
设置当时事务的只读标示,等同于connection.setReadOnly()
关键名词解释
名词 | 概念 |
---|---|
PlatformTransactionManager | 事务管理器,管理事务的各生命周期方法,简称 TxMgr |
TransactionAttribute | 事务属性, 包含隔离级别,传播行为, 是否只读等信息,简称 TxAttr |
TransactionStatus | 事务状态,包含当前事务、挂起等信息,简称 TxStatus |
TransactionInfo | 事务信息,内含 TxMgr, TxAttr, TxStatus 等信息,简称 TxInfo |
TransactionSynchronization | 事务同步回调,内含多个钩子方法,简称 TxSync / transaction synchronization |
TransactionSynchronizationManager | 事务同步管理器,维护当前线程事务资源,信息以及 TxSync 集合 |
基本原理
public void execute(TxCallback txCallback){
//获取连接
Connection connection = acquireConnection();
try{
//执行业务代码
doInService();
//提交事务
connection.commit();
}catch (Exception e){
//回滚事务
rollback(connection);
}finally {
//释放连接
releaseConnection(connection);
}
}
Spring事务管理的基本原理就是以上代码,获取连接 -> 执行代码 -> 提交/回滚事务。Spring只是将这个流程给抽象出来了,所有事务相关的操作都交由TransactionManager去实现,然后封装一个模板形式的入口来执行t
比如org.springframework.transaction.support.TransactionTemplate
的实现:
@Override
public <T> T execute(TransactionCallback<T> action) throws TransactionException {
if (this.transactionManager instanceof CallbackPreferringPlatformTransactionManager) {
return ((CallbackPreferringPlatformTransactionManager) this.transactionManager).execute(this, action);
}
else {
//通过事务管理器获取事务
TransactionStatus status = this.transactionManager.getTransaction(this);
T result;
try {
//执行业务代码
result = action.doInTransaction(status);
}
//处理异常回滚
catch (RuntimeException ex) {
// Transactional code threw application exception -> rollback
rollbackOnException(status, ex);
throw ex;
}
catch (Error err) {
// Transactional code threw error -> rollback
rollbackOnException(status, err);
throw err;
}
catch (Exception ex) {
// Transactional code threw unexpected exception -> rollback
rollbackOnException(status, ex);
throw new UndeclaredThrowableException(ex, "TransactionCallback threw undeclared checked exception");
}
//提交事务
this.transactionManager.commit(status);
return result;
}
}
注解形式的事务(@Transactional),实现机制也是一样,基于Spring的AOP,将上面Template的模式换成了自动的AOP,在AOP的Interceptor(org.springframework.transaction.interceptor.TransactionInterceptor
)中来执行这套流程:
protected Object invokeWithinTransaction(Method method, Class<?> targetClass, final InvocationCallback invocation)
throws Throwable {
// If the transaction attribute is null, the method is non-transactional.
final TransactionAttribute txAttr = getTransactionAttributeSource().getTransactionAttribute(method, targetClass);
//获取事务管理器
final PlatformTransactionManager tm = determineTransactionManager(txAttr);
final String joinpointIdentification = methodIdentification(method, targetClass);
if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {
// Standard transaction demarcation with getTransaction and commit/rollback calls.
//创建事务
TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
Object retVal = null;
try {
// This is an around advice: Invoke the next interceptor in the chain.
// This will normally result in a target object being invoked.
//执行被“AOP”的代码
retVal = invocation.proceedWithInvocation();
}
catch (Throwable ex) {
// target invocation exception
//处理异常回滚
completeTransactionAfterThrowing(txInfo, ex);
throw ex;
}
finally {
//清除资源
cleanupTransactionInfo(txInfo);
}
//提交事务
commitTransactionAfterReturning(txInfo);
return retVal;
}
....
}
复杂流程下的事务传播/保持相同事务的关键:
对于复杂一些的业务流程,会出现各种类之间的调用,Spring是如何做到保持同一个事务的?
**
其实基本原理很简单,只需要将当前事务(Connection)隐式的保存至事务管理器内,后续方法在执行JDBC操作前,从事务管理器内获取即可:
比如HibernateTemplate
中的SessionFactory
中的getCurrentSession
,这里的getCurrentSession
就是从(可能是间接的)Spring事务管理器中获取的
Spring事务管理器将处理事务时的相关临时资源(Connection等)存在org.springframework.transaction.support.TransactionSynchronizationManager
中,通过ThreadLocal维护
public abstract class TransactionSynchronizationManager {
private static final Log logger = LogFactory.getLog(TransactionSynchronizationManager.class);
private static final ThreadLocal<Map<Object, Object>> resources =
new NamedThreadLocal<Map<Object, Object>>("Transactional resources");
private static final ThreadLocal<Set<TransactionSynchronization>> synchronizations =
new NamedThreadLocal<Set<TransactionSynchronization>>("Transaction synchronizations");
private static final ThreadLocal<String> currentTransactionName =
new NamedThreadLocal<String>("Current transaction name");
private static final ThreadLocal<Boolean> currentTransactionReadOnly =
new NamedThreadLocal<Boolean>("Current transaction read-only status");
private static final ThreadLocal<Integer> currentTransactionIsolationLevel =
new NamedThreadLocal<Integer>("Current transaction isolation level");
private static final ThreadLocal<Boolean> actualTransactionActive =
new NamedThreadLocal<Boolean>("Actual transaction active");
...
}
针对一些复杂场景,嵌套事务+独立事务,涉及到挂起(suspend),恢复(resume)的情况,相关资源也是存储在TransactionSynchronizationManager
中的,方便嵌套事务的处理。
比如A->B时,A方法已经开启了事务,并将当前事务资源绑定在TransactionSynchronizationManager
,那么执行B之前,会检测当前是否已经存在事务;检测方式就是从TransactionSynchronizationManager
查找并检测状态,如果已经在事务内,那么就根据不同的传播行为配置来执行不同的逻辑,对于REQUIRES_NEW等传播行为的处理会麻烦一些,会涉及到“挂起(suspend)”和恢复(resume)的操作,原理打通小异,这里就不做过多解释了
常见问题
事务没生效
有下列代码,入口为test方法,在testTx方法中配置了@Transactional注解,同时在插入数据后抛出RuntimeException异常,但是方法执行后插入的数据并没有回滚,竟然插入成功了
public void test(){
testTx();
}
@Transactional
public void testTx(){
UrlMappingEntity urlMappingEntity = new UrlMappingEntity();
urlMappingEntity.setUrl("http://www.baidu.com");
urlMappingEntity.setExpireIn(777l);
urlMappingEntity.setCreateTime(new Date());
urlMappingRepository.save(urlMappingEntity);
if(true){
throw new RuntimeException();
}
}
这里不生效的原因是因为入口的方法/类没有增加@Transaction注解,由于Spring的事务管理器也是基于AOP实现的,不管是Cglib(ASM)还是Jdk的动态代理,本质上也都是子类机制;在同类之间的方法调用会直接调用本类代码,不会执行动态代理曾的代码;所以在这个例子中,由于入口方法test
没有增加代理注解,所以textTx
方法上增加的事务注解并不会生效
异步后事务失效
比如在一个事务方法中,开启了子线程操作库,那么此时子线程的事务和主线程事务是不同的。
因为在Spring的事务管理器中,事务相关的资源(连接,session,事务状态之类)都是存放在TransactionSynchronizationManager中的,通过ThreadLocal存放,如果跨线程的话就无法保证一个事务了
# TransactionSynchronizationManager.java
private static final ThreadLocal<Map<Object, Object>> resources =
new NamedThreadLocal<>("Transactional resources");
private static final ThreadLocal<Set<TransactionSynchronization>> synchronizations =
new NamedThreadLocal<>("Transaction synchronizations");
private static final ThreadLocal<String> currentTransactionName =
new NamedThreadLocal<>("Current transaction name");
private static final ThreadLocal<Boolean> currentTransactionReadOnly =
new NamedThreadLocal<>("Current transaction read-only status");
private static final ThreadLocal<Integer> currentTransactionIsolationLevel =
new NamedThreadLocal<>("Current transaction isolation level");
private static final ThreadLocal<Boolean> actualTransactionActive =
new NamedThreadLocal<>("Actual transaction active");
事务提交失败
org.springframework.transaction.UnexpectedRollbackException:
Transaction silently rolled back because it has been marked as rollback-only
这个异常是由于在同一个事务内,多个事务方法之间调用,子方法抛出异常,但又被父方法忽略了导致的。
因为子方法抛出了异常,Spring事务管理器会将当前事务标为失败状态,准备进行回滚,可是当子方法执行完毕出栈后,父方法又忽略了此异常,待方法执行完毕后正常提交时,事务管理器会检查回滚状态,若有回滚标示则抛出此异常。具体可以参考org.springframework.transaction.support.AbstractPlatformTransactionManager#processCommit
示例代码:
A -> B
# A Service(@Transactional):
public void testTx(){
urlMappingRepo.deleteById(98l);
try{
txSubService.testSubTx();
}catch (Exception e){
e.printStackTrace();
}
}
# B Service(@Transactional)
public void testSubTx(){
if(true){
throw new RuntimeException();
}
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。